All files / local reference_set.ts

95.45% Statements 63/66
100% Branches 8/8
90.48% Functions 19/21
98.25% Lines 56/57
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156                                  2x 2x 2x 2x         2x                                 2x   1591x     1591x     1591x     2x 17x       2x 824x 824x 824x       1072x 1072x             2x 169x     1052x 1052x             707x 707x 707x 707x 707x 433x       2x       2x 602x 602x 602x 484x       2x 218x 218x 218x 218x 218x 20x   218x     2x 1312x     2x       1417x 1417x 1417x       2x   2x   7915x 7915x       2x 9750x             2x 5035x         2x  
/**
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
import { BatchId, TargetId } from '../core/types';
import { documentKeySet, DocumentKeySet } from '../model/collections';
import { DocumentKey } from '../model/document_key';
import { primitiveComparator } from '../util/misc';
import { SortedSet } from '../util/sorted_set';
 
import { GarbageCollector } from './garbage_collector';
import { GarbageSource } from './garbage_source';
import { PersistenceTransaction } from './persistence';
import { PersistencePromise } from './persistence_promise';
 
/**
 * A collection of references to a document from some kind of numbered entity
 * (either a target ID or batch ID). As references are added to or removed from
 * the set corresponding events are emitted to a registered garbage collector.
 *
 * Each reference is represented by a DocumentReference object. Each of them
 * contains enough information to uniquely identify the reference. They are all
 * stored primarily in a set sorted by key. A document is considered garbage if
 * there's no references in that set (this can be efficiently checked thanks to
 * sorting by key).
 *
 * ReferenceSet also keeps a secondary set that contains references sorted by
 * IDs. This one is used to efficiently implement removal of all references by
 * some target ID.
 */
export class ReferenceSet implements GarbageSource {
  // A set of outstanding references to a document sorted by key.
  private refsByKey = new SortedSet(DocReference.compareByKey);
 
  // A set of outstanding references to a document sorted by target id.
  private refsByTarget = new SortedSet(DocReference.compareByTargetId);
 
  /** Keeps track of keys that have references */
  private garbageCollector: GarbageCollector | null = null;
 
  /** Returns true if the reference set contains no references. */
  isEmpty(): boolean {
    return this.refsByKey.isEmpty();
  }
 
  /** Adds a reference to the given document key for the given ID. */
  addReference(key: DocumentKey, id: TargetId | BatchId): void {
    const ref = new DocReference(key, id);
    this.refsByKey = this.refsByKey.add(ref);
    this.refsByTarget = this.refsByTarget.add(ref);
  }
 
  /** Add references to the given document keys for the given ID. */
  addReferences(keys: DocumentKeySet, id: TargetId | BatchId): void {
    keys.forEach(key => this.addReference(key, id));
  }
 
  /**
   * Removes a reference to the given document key for the given
   * ID.
   */
  removeReference(key: DocumentKey, id: TargetId | BatchId): void {
    this.removeRef(new DocReference(key, id));
  }
 
  removeReferences(keys: DocumentKeySet, id: TargetId | BatchId): void {
    keys.forEach(key => this.removeReference(key, id));
  }
 
  /**
   * Clears all references with a given ID. Calls removeRef() for each key
   * removed.
   */
  removeReferencesForId(id: TargetId | BatchId): void {
    const emptyKey = DocumentKey.EMPTY;
    const startRef = new DocReference(emptyKey, id);
    const endRef = new DocReference(emptyKey, id + 1);
    this.refsByTarget.forEachInRange([startRef, endRef], ref => {
      this.removeRef(ref);
    });
  }
 
  removeAllReferences(): void {
    this.refsByKey.forEach(ref => this.removeRef(ref));
  }
 
  private removeRef(ref: DocReference): void {
    this.refsByKey = this.refsByKey.delete(ref);
    this.refsByTarget = this.refsByTarget.delete(ref);
    if (this.garbageCollector !== null) {
      this.garbageCollector.addPotentialGarbageKey(ref.key);
    }
  }
 
  referencesForId(id: TargetId | BatchId): DocumentKeySet {
    const emptyKey = DocumentKey.EMPTY;
    const startRef = new DocReference(emptyKey, id);
    const endRef = new DocReference(emptyKey, id + 1);
    let keys = documentKeySet();
    this.refsByTarget.forEachInRange([startRef, endRef], ref => {
      keys = keys.add(ref.key);
    });
    return keys;
  }
 
  setGarbageCollector(garbageCollector: GarbageCollector | null): void {
    this.garbageCollector = garbageCollector;
  }
 
  containsKey(
    txn: PersistenceTransaction | null,
    key: DocumentKey
  ): PersistencePromise<boolean> {
    const ref = new DocReference(key, 0);
    const firstRef = this.refsByKey.firstAfterOrEqual(ref);
    return PersistencePromise.resolve(
      firstRef !== null && key.isEqual(firstRef.key)
    );
  }
}
 
export class DocReference {
  constructor(
    public key: DocumentKey,
    public targetOrBatchId: TargetId | BatchId
  ) {}
 
  /** Compare by key then by ID */
  static compareByKey(left: DocReference, right: DocReference): number {
    return (
      DocumentKey.comparator(left.key, right.key) ||
      primitiveComparator(left.targetOrBatchId, right.targetOrBatchId)
    );
  }
 
  /** Compare by ID then by key */
  static compareByTargetId(left: DocReference, right: DocReference): number {
    return (
      primitiveComparator(left.targetOrBatchId, right.targetOrBatchId) ||
      DocumentKey.comparator(left.key, right.key)
    );
  }
}